#include "astralapi.h"
#include "queueapi.h"
#include "task.h"


typedef struct Sem
{
    AstralSem m;
    struct Queue blockq;
} *Sem;

static Sem get_sem_wrapper(AstralSem sem)
{
    static struct Sem mwa[100];
    return &mwa[sem->id];
}




/*****************************************************************************/
/**                                Sem API                                **/
/*****************************************************************************/

void create_sem(AstralSem sem)
{
    static unsigned sem_id = 0;
    sem->player = NULL;
    sem->id = sem_id++;
    Sem mw = get_sem_wrapper(sem);
    queue_init(&mw->blockq);
    mw->m = sem;
}

boolean obtain_sem(AstralSem sem)
{
    AstralTask self = get_curr_task();
    Sem mw = get_sem_wrapper(sem);

    while (1)
    {
        word status = arch_disable_local_irq();

        while (!arch_compare_and_swap(&sem->player,
                                      CAST_AS(word, NULL),
                                      CAST_AS(word, self)))
        {
        }
        if (sem->value > 0)
        {
            sem->value--;
            sem->player = NULL;
            arch_enable_local_irq(status);
            return True;
        }
        else
        {
            boolean r = queue_push(&mw->blockq, &self->sus_on);
            sem->player = NULL;
            if(!r)
            {
                kerror("push block sem error\n");
            }
            arch_enable_local_irq(status);
            state_change(self, BLOCKED, True);
        }
    }

    return True;
}

boolean release_sem(AstralSem sem)
{
    AstralTask self = get_curr_task();
    word status = arch_disable_local_irq();

    Sem mw = get_sem_wrapper(sem);
    // resume the tasks blocked on this sem
    // tasks on other core may play woth this block Q
    // so critical here, lock here
    while (!arch_compare_and_swap(&sem->player,
                                  CAST_AS(word, NULL),
                                  CAST_AS(word, self)))
    {
    }

    sem->value++;

    boolean need_sched = (mw->blockq.num > 0);

    while (mw->blockq.num)
    {
        // resume all blocked task
        QueueNode n = queue_pop(&mw->blockq);
        if(!n)
        {
            kerror("error sem node \n");
            while (1)
            {
            }
        }
        AstralTask task = CONTAINER_OF(AstralTask, sus_on, n);
        state_change(task, RESUMED, False);
    }

    arch_compare_and_swap(&sem->player,
                          CAST_AS(word, self),
                          CAST_AS(word, NULL));

    arch_enable_local_irq(status);

    if (need_sched)
    {
        state_change(self, PREEMPTED, True);
    }
    return True;
}
